#include <boost/lexical_cast.hpp>
#include "../Parameters.hpp"
#include "Membership.hpp"
#include "../util/tokenize.hpp"

using namespace BFT::membership;

Membership::Membership(std::string _configFileName, BFT::util::Role _role, int _id)
{
	//TODO: do something here to add a security provider ????
	
	role=_role;
	id=_id;
	
	std::string propertyName;
	std::string propertyValue;
	std::vector<std::string> tokens;
	
	std::string privKeyFileName = "./keys/";
	privKeyFileName.append(BFT::util::roleToString(role));
	privKeyFileName.append( boost::lexical_cast<std::string>(id) );
	privKeyFileName.append(".privk.properties");
	//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
	//read the key file
	boost::shared_ptr<Properties> privKeyProp (new Properties(privKeyFileName));
	
	myPrivateKeyString = privKeyProp.get()->getString("PRIV");
	if (myPrivateKeyString.compare("")==0)
	{	
		fprintf(stderr,"unable to obtain the private key\n");
		exit(22);
	}
	
	//read the configuration file
	boost::shared_ptr<Properties> properties (new Properties(_configFileName));
	
	BFT::Parameters::toleratedOrderCrashes=properties.get()->getInt("orderCrashFailures");
	BFT::Parameters::toleratedOrderLiars=properties.get()->getInt("orderLiarFailures");	
	BFT::Parameters::toleratedExecutionCrashes=properties.get()->getInt("execCrashFailures");
	BFT::Parameters::toleratedExecutionLiars=properties.get()->getInt("execLiarFailures");
	BFT::Parameters::filtered=properties.get()->getBool("filtered");
	if(BFT::Parameters::filtered)
	{
		BFT::Parameters::toleratedFilterCrashes=properties.get()->getInt("filterCrashFailures");
		BFT::Parameters::toleratedFilterLiars=properties.get()->getInt("filterLiarFailures");
	}
	BFT::Parameters::insecure=properties.get()->getBool("insecure");
	BFT::Parameters::numberOfClients=properties.get()->getInt("clientCount");

	int offset=0; //tells you from which column to read
	
	switch (role)
	{
		case BFT::util::CLIENT:
			checkIdWithinRange(BFT::Parameters::getNumberOfClients());
			offset=0;
			break;
		case BFT::util::FILTER:
			checkIdWithinRange(BFT::Parameters::getFilterCount());
			offset= 1 + id;
			break;
		case BFT::util::ORDER:
			checkIdWithinRange(BFT::Parameters::getOrderCount());
			offset= 1 + BFT::Parameters::getFilterCount() +id;
			break;			
		case BFT::util::EXEC:
			checkIdWithinRange(BFT::Parameters::getExecutionCount());
			offset= 1 + BFT::Parameters::getFilterCount() + BFT::Parameters::getOrderCount() + id;
			break;
		default:
			fprintf(stderr,"unknown role...\n");
			exit(22);
	}
	
	int interfaceCount = 1 + 
				BFT::Parameters::getFilterCount() + 
				BFT::Parameters::getOrderCount() + 
				BFT::Parameters::getExecutionCount();
	
	filterNodes.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//filterNodes.get()->reserve(BFT::Parameters::getFilterCount());
	for (int i=0; i< BFT::Parameters::getFilterCount(); i++)
	{
		propertyName="FILTER.";
		propertyName.append(boost::lexical_cast<std::string>(i));
		propertyValue=properties.get()->getString(propertyName);
		tokens = BFT::util::tokenize_str(propertyValue," ");
		boost::shared_ptr<Principal> tmp;
		filterNodes.get()->push_back(tmp);
		filterNodes.get()->at(i).reset(new Principal( tokens[offset] , tokens[interfaceCount]) );
	}

	orderNodes.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//orderNodes.get()->reserve(BFT::Parameters::getOrderCount());
	for (int i=0; i< BFT::Parameters::getOrderCount(); i++)
	{
		propertyName="ORDER.";
		propertyName.append(boost::lexical_cast<std::string>(i));
		propertyValue=properties.get()->getString(propertyName);
		tokens = BFT::util::tokenize_str(propertyValue," ");
		boost::shared_ptr<Principal> tmp;
		orderNodes.get()->push_back(tmp);
		orderNodes.get()->at(i).reset(new Principal( tokens[offset] , tokens[interfaceCount]) );
	}
	
	execNodes.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//execNodes.get()->reserve(BFT::Parameters::getExecutionCount());
	for (int i=0; i< BFT::Parameters::getExecutionCount(); i++)
	{
		propertyName="EXEC.";
		propertyName.append(boost::lexical_cast<std::string>(i));
		propertyValue=properties.get()->getString(propertyName);
		tokens = BFT::util::tokenize_str(propertyValue," ");
		boost::shared_ptr<Principal> tmp;
		execNodes.get()->push_back(tmp);
		execNodes.get()->at(i).reset(new Principal( tokens[offset] , tokens[interfaceCount]) );
	}
	
	clientNodes.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//clientNodes.get()->reserve(BFT::Parameters::getNumberOfClients());
	for (int i=0; i< BFT::Parameters::getNumberOfClients(); i++)
	{
		propertyName="CLIENT.";
		propertyName.append(boost::lexical_cast<std::string>(i));
		propertyValue=properties.get()->getString(propertyName);
		tokens = BFT::util::tokenize_str(propertyValue," ");
		boost::shared_ptr<Principal> tmp;
		clientNodes.get()->push_back(tmp);
		clientNodes.get()->at(i).reset(new Principal( tokens[offset] , tokens[interfaceCount]) );
	}
	
	propertyName=BFT::util::roleToString(role);
	propertyName.append(".");
	propertyName.append(boost::lexical_cast<std::string>(id));
	propertyValue=properties.get()->getString(propertyName);
	tokens = BFT::util::tokenize_str(propertyValue," ");
	
	myInterfaces.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//myInterfaces.get()->reserve(1 + BFT::Parameters::getExecutionCount()
	//			      +	BFT::Parameters::getOrderCount()
	//				      + BFT::Parameters::getFilterCount());
	for (unsigned int i=0; i<tokens.size()-1;i++)
	{
		boost::shared_ptr<Principal> tmp (new Principal(tokens[i],tokens[tokens.size()-1]));
		myInterfaces.get()->push_back(tmp);
		//fprintf(stderr,"added one interface to myInterfaces. size =%d\n",myInterfaces.get()->size());
	}
	
	myPublicKeyString = tokens[tokens.size()-1];
	//TODO:
	//do something now to fill in myPrivateKey e myPublicKey
	
	for(unsigned int i=0; i<clientNodes.get()->size();i++)
	{
		privKeyFileName = "./keys/CLIENT";
		privKeyFileName.append(boost::lexical_cast<std::string>(i));
		privKeyFileName.append(".privk.properties");
		//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
		privKeyProp.reset(new Properties(privKeyFileName));
		//TODO
		//do something to fill the mac
		//clientNodes.get()->at(i).get()->setMac..... privateKeyProp.get()->getString("SECRET")
	}

	for(unsigned int i=0; i<filterNodes.get()->size();i++)
	{
		privKeyFileName = "./keys/FILTER";
		privKeyFileName.append(boost::lexical_cast<std::string>(i));
		privKeyFileName.append(".privk.properties");
		//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
		privKeyProp.reset(new Properties(privKeyFileName));
		//TODO
		//do something to fill the mac
		//filterNodes.get()->at(i).get()->setMac..... privateKeyProp.get()->getString("SECRET")
	}

	for(unsigned int i=0; i<execNodes.get()->size();i++)
	{
		privKeyFileName = "./keys/EXEC";
		privKeyFileName.append(boost::lexical_cast<std::string>(i));
		privKeyFileName.append(".privk.properties");
		//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
		privKeyProp.reset(new Properties(privKeyFileName));
		//TODO
		//do something to fill the mac
		//execNodes.get()->at(i).get()->setMac..... privateKeyProp.get()->getString("SECRET")
	}

	for(unsigned int i=0; i<orderNodes.get()->size();i++)
	{
		privKeyFileName = "./keys/ORDER";
		privKeyFileName.append(boost::lexical_cast<std::string>(i));
		privKeyFileName.append(".privk.properties");
		//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
		privKeyProp.reset(new Properties(privKeyFileName));
		//TODO
		//do something to fill the mac
		//orderNodes.get()->at(i).get()->setMac..... privateKeyProp.get()->getString("SECRET")
	}

	privKeyFileName = "./keys/";
	privKeyFileName.append(BFT::util::roleToString(role));
	//privKeyFileName.append(".");
	privKeyFileName.append(boost::lexical_cast<std::string>(id));
	privKeyFileName.append(".privk.properties");
	//fprintf(stderr,"looking for private key file %s\n",privKeyFileName.c_str());
	privKeyProp.reset(new Properties(privKeyFileName));
	//TODO
	//do something to fill the mac
	//orderNodes.get()->at(i).get()->setMac..... privateKeyProp.get()->getString("SECRET")

	//Support for different threads handling the network interaction with each role
	propertyName = BFT::util::roleToString(role);
	propertyName.append(".");
	propertyName.append(boost::lexical_cast<std::string>(id));
	
	propertyValue = properties.get()->getString(propertyName);
	tokens = BFT::util::tokenize_str(propertyValue, " ");
	
	myOrderInterfaces.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//myOrderInterfaces.get()->reserve(BFT::Parameters::getOrderCount());
	myExecInterfaces.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//myExecInterfaces.get()->reserve(BFT::Parameters::getExecutionCount());
	myClientInterfaces.reset(new std::vector< boost::shared_ptr < Principal > >() );
	//myClientInterfaces.get()->reserve(1);
	
	boost::shared_ptr<Principal> tmp (new Principal( tokens[0], tokens[tokens.size()-1] ));
	myClientInterfaces.get()->push_back(tmp);
	
	for(int i = 0; i < BFT::Parameters::getOrderCount(); i++)
	{
		tmp.reset(new Principal
			( tokens[ 
				1 + 
				BFT::Parameters::getFilterCount() + 
				i ] 
			, tokens[tokens.size() -1]) );
			
		myOrderInterfaces.get()->push_back(tmp);	
	}
	
	for (int i=0; i < BFT::Parameters::getExecutionCount(); i++)
	{
		tmp.reset(new Principal
			(tokens[
				1 +
				BFT::Parameters::getFilterCount() +
				BFT::Parameters::getOrderCount() +
				i ]
			, tokens[tokens.size()-1]) );
		myExecInterfaces.get()->push_back(tmp);
	}
	
	//myFilterInterfaces.get()->reserve(BFT::Parameters::getFilterCount());
	myFilterInterfaces.reset(new std::vector< boost::shared_ptr < Principal > >() );
	
	for (int i=0; i<BFT::Parameters::getFilterCount(); i++)
	{
		tmp.reset(new Principal
			(tokens[
				1 +
				i]
			, tokens[tokens.size()-1]) );
		myFilterInterfaces.get()->push_back(tmp);
	}	
	
}

void Membership::checkIdWithinRange(int _range)
{
	if (id < 0 || id >= _range)
	{
		fprintf(stderr,"Id is not within range! id: %d, range: %d\n",id,_range);
		exit(23);
	}
}

boost::shared_ptr< std::vector< boost::shared_ptr<Principal> > > Membership::getMyInterfaces() 
{
	return myInterfaces;
}

boost::shared_ptr< std::vector< boost::shared_ptr<Principal> > > Membership::getMyInterfaces(BFT::util::Role _role)
{
	switch(_role)
	{
		case BFT::util::FILTER:
			return myFilterInterfaces;
		case BFT::util::ORDER:
			return myOrderInterfaces;
		case BFT::util::EXEC:
			return myExecInterfaces;
		case BFT::util::CLIENT:
			return myClientInterfaces;
		default:
			fprintf(stderr,"unable to getMyInterfaces, unknown role! role: %d\n",_role);
			exit(24);	
	}
}

boost::shared_ptr<Principal> Membership::getNode(BFT::util::Role _role, int _id)
{
	switch(_role)
	{
		case BFT::util::FILTER:
			return getFilterNode(_id);
		case BFT::util::ORDER:
			return getOrderNode(_id);
		case BFT::util::EXEC:
			return getExecNode(_id);
		case BFT::util::CLIENT:
			return getClientNode(_id);
		default:
			fprintf(stderr,"unable to getNode, unknown role! role: %d\n",_role);
			exit(34);	
	}
}

